Spring JdbcTemplate详解,这都看不懂就安心去当个咸鱼吧!

您所在的位置:网站首页 jdbctemplate 手动提交 Spring JdbcTemplate详解,这都看不懂就安心去当个咸鱼吧!

Spring JdbcTemplate详解,这都看不懂就安心去当个咸鱼吧!

2024-01-27 10:11| 来源: 网络整理| 查看: 265

JDBC 基础

Java程序使用JDBC接口访问关系数据库的时候,需要以下几步:

创建全局DataSource实例,表示数据库连接池; 在需要读写数据库的方法内部,按如下步骤访问数据库: 从全局DataSource实例获取Connection实例; 通过Connection实例创建PreparedStatement实例; 执行SQL语句,如果是查询,则通过ResultSet读取结果集,如果是修改,则获得int结果。

正确编写JDBC代码的关键是使用try ... finally释放资源,涉及到事务的代码需要正确提交或回滚事务。

在Spring使用JDBC,首先我们通过IoC容器创建并管理一个DataSource实例,然后,Spring提供了一个JdbcTemplate,可以方便地让我们操作JDBC,因此,通常情况下,我们会实例化一个JdbcTemplate。顾名思义,这个类主要使用了Template模式。

编写示例代码或者测试代码时,我们强烈推荐使用HSQLDB这个数据库,它是一个用Java编写的关系数据库,可以以内存模式或者文件模式运行,本身只有一个jar包,非常适合演示代码或者测试代码,本文使用MySql

我们以实际工程为例,先创建Maven工程spring-data-jdbc,然后引入以下依赖:

org.springframework spring-context 5.2.0.RELEASE mysql mysql-connector-java 8.0.15 org.springframework spring-jdbc 5.2.0.RELEASE javax.annotation javax.annotation-api 1.3.2 com.zaxxer HikariCP 3.4.2 org.hsqldb hsqldb 2.5.0

在AppConfig中,我们需要创建以下几个必须的Bean:

@Configuration @ComponentScan @PropertySource("jdbc.properties") public class AppConfig { @Value("${jdbc.url}") String jdbcUrl; @Value("${jdbc.username}") String jdbcUsername; @Value("${jdbc.password}") String jdbcPassword; @Bean DataSource createDataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); config.addDataSourceProperty("autoCommit", "true"); config.addDataSourceProperty("connectionTimeout", "5"); config.addDataSourceProperty("idleTimeout", "60"); return new HikariDataSource(config); } @Bean JdbcTemplate createJdbcTemplate(@Autowired DataSource dataSource) { return new JdbcTemplate(dataSource); } }

在上述配置中:

通过@PropertySource("jdbc.properties")读取数据库配置文件; 通过@Value("${jdbc.url}")注入配置文件的相关配置; 创建一个DataSource实例,它的实际类型是HikariDataSource,创建时需要用到注入的配置; 创建一个JdbcTemplate实例,它需要注入DataSource,这是通过方法参数完成注入的。

最后,针对HSQLDB写一个配置文件jdbc.properties:

# 数据库文件名为testdb: jdbc.url=jdbc:hsqldb:file:testdb # Hsqldb默认的用户名是sa,口令是空字符串: jdbc.username=sa jdbc.password= # mysql配置 # 数据库文件名为testdb: jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/springjdbc?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false jdbc.user=root jdbc.password=1234

可以通过HSQLDB自带的工具来初始化数据库表,这里我们写一个Bean,在Spring容器启动时自动创建一个users表:

@Component public class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; @PostConstruct public void init() { jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" // + "id BIGINT IDENTITY NOT NULL PRIMARY KEY, " // + "email VARCHAR(100) NOT NULL, " // + "password VARCHAR(100) NOT NULL, " // + "name VARCHAR(100) NOT NULL, " // + "UNIQUE (email))"); } }

现在,所有准备工作都已完毕。我们只需要在需要访问数据库的Bean中,注入JdbcTemplate即可:

@Component public class UserService { @Autowired JdbcTemplate jdbcTemplate; ... } JdbcTemplate 用法

Spring提供的JdbcTemplate采用Template模式,提供了一系列以回调为特点的工具方法,目的是避免繁琐的try...catch语句。

我们以具体的示例来说明JdbcTemplate的用法。

首先我们看T execute(ConnectionCallback action)方法,它提供了Jdbc的Connection供我们使用:

public User getUserById(long id) { // 注意传入的是ConnectionCallback: return jdbcTemplate.execute((Connection conn) -> { // 可以直接使用conn实例,不要释放它,回调结束后JdbcTemplate自动释放: // 在内部手动创建的PreparedStatement、ResultSet必须用try(...)释放: try (var ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) { ps.setObject(1, id); try (var rs = ps.executeQuery()) { if (rs.next()) { return new User( // new User object: rs.getLong("id"), // id rs.getString("email"), // email rs.getString("password"), // password rs.getString("name")); // name } throw new RuntimeException("user not found by id."); } } }); }

也就是说,上述回调方法允许获取Connection,然后做任何基于Connection的操作。

我们再看T execute(String sql, PreparedStatementCallback action)的用法:

public User getUserByName(String name) { // 需要传入SQL语句,以及PreparedStatementCallback: return jdbcTemplate.execute("SELECT * FROM users WHERE name = ?", (PreparedStatement ps) -> { // PreparedStatement实例已经由JdbcTemplate创建,并在回调后自动释放: ps.setObject(1, name); try (var rs = ps.executeQuery()) { if (rs.next()) { return new User( // new User object: rs.getLong("id"), // id rs.getString("email"), // email rs.getString("password"), // password rs.getString("name")); // name } throw new RuntimeException("user not found by id."); } }); }

最后,我们看T queryForObject(String sql, Object[] args, RowMapper rowMapper)方法:

public User getUserByEmail(String email) { // 传入SQL,参数和RowMapper实例: return jdbcTemplate.queryForObject("SELECT * FROM users WHERE email = ?", new Object[] { email }, (ResultSet rs, int rowNum) -> { // 将ResultSet的当前行映射为一个JavaBean: return new User( // new User object: rs.getLong("id"), // id rs.getString("email"), // email rs.getString("password"), // password rs.getString("name")); // name }); }

RowMapper定义:

@FunctionalInterface public interface RowMapper { @Nullable T mapRow(ResultSet var1, int var2) throws SQLException; }

方法getUserByEmail中(ResultSet rs, int rowNum) -> {}语句实现一个RowMapper继承类的对象

在queryForObject()方法中,传入SQL以及SQL参数后,JdbcTemplate会自动创建PreparedStatement,自动执行查询并返回ResultSet,我们提供的RowMapper需要做的事情就是把ResultSet的当前行映射成一个JavaBean并返回。整个过程中,使用Connection、PreparedStatement和ResultSet都不需要我们手动管理。

RowMapper不一定返回JavaBean,实际上它可以返回任何Java对象。例如,使用SELECT COUNT(*)查询时,可以返回Long:

public long getUsers() { return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", null, (ResultSet rs, int rowNum) -> { // SELECT COUNT(*)查询只有一列,取第一列数据: return rs.getLong(1); }); }

如果我们期望返回多行记录,而不是一行,可以用query()方法:

public List getUsers(int pageIndex) { int limit = 100; int offset = limit * (pageIndex - 1); return jdbcTemplate.query("SELECT * FROM users LIMIT ? OFFSET ?", new Object[] { limit, offset }, new BeanPropertyRowMapper(User.class)); }

上述query()方法传入的参数仍然是SQL、SQL参数以及RowMapper实例。这里我们直接使用Spring提供的BeanPropertyRowMapper。如果数据库表的结构恰好和JavaBean的属性名称一致,那么BeanPropertyRowMapper就可以直接把一行记录按列名转换为JavaBean。

如果我们执行的不是查询,而是插入、更新和删除操作,那么需要使用update()方法:

public void updateUser(User user) { // 传入SQL,SQL参数,返回更新的行数: if (1 != jdbcTemplate.update("UPDATE user SET name = ? WHERE id=?", user.getName(), user.getId())) { throw new RuntimeException("User not found by id"); } }

只有一种INSERT操作比较特殊,那就是如果某一列是自增列(例如自增主键),通常,我们需要获取插入后的自增值。JdbcTemplate提供了一个KeyHolder来简化这一操作:

public User register(String email, String password, String name) { // 创建一个KeyHolder: KeyHolder holder = new GeneratedKeyHolder(); if (1 != jdbcTemplate.update( // 参数1:PreparedStatementCreator (conn) -> { // 创建PreparedStatement时,必须指定RETURN_GENERATED_KEYS: var ps = conn.prepareStatement("INSERT INTO users(email,password,name) VALUES(?,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setObject(1, email); ps.setObject(2, password); ps.setObject(3, name); return ps; }, // 参数2:KeyHolder holder) ) { throw new RuntimeException("Insert failed."); } // 从KeyHolder中获取返回的自增值: return new User(holder.getKey().longValue(), email, password, name); }

JdbcTemplate还有许多重载方法,这里我们不一一介绍。需要强调的是,JdbcTemplate只是对JDBC操作的一个简单封装,它的目的是尽量减少手动编写try(resource) {...}的代码,对于查询,主要通过RowMapper实现了JDBC结果集到Java对象的转换。

我们总结一下JdbcTemplate的用法,那就是:

针对简单查询,优选query()和queryForObject(),因为只需提供SQL语句、参数和RowMapper; 针对更新操作,优选update(),因为只需提供SQL语句和参数; 任何复杂的操作,最终也可以通过execute(ConnectionCallback)实现,因为拿到Connection就可以做任何JDBC操作。

实际上我们使用最多的仍然是各种查询。如果在设计表结构的时候,能够和JavaBean的属性一一对应,那么直接使用BeanPropertyRowMapper就很方便。如果表结构和JavaBean不一致怎么办?那就需要稍微改写一下查询,使结果集的结构和JavaBean保持一致。

例如,表的列名是office_address,而JavaBean属性是workAddress,就需要指定别名,改写查询如下:

SELECT id, email, office_address AS workAddress, name FROM users WHERE email = ? RowMapper 的基本使用

使用过原生JDBC的朋友应该知道,从数据库查询出来的记录全都被保存在ResultSet结果集中,我们需要将结果集中的数据一条条地获取并设置到具体的实体类上,如此,该实体类才能在接下来的程序中使用。然而问题是,每次都要这么操作实在是太麻烦了,Spring就不应该提供什么功能来替我们做这些事情吗?答案当然是有的,那就是本文的主角——RowMapper。

Spring JDBC中目前有两个主要的RowMapper实现,使用它们应该能解决大部分的场景了。

SingleColumnRowMapper

通过名字我们就能大概了解,在查询返回单列数据的时候,就该使用这个RowMapper,下面我们来看看具体的代码:

@Override public String getStudentNameById(String id) { String sql = "select name from test_student where id = ?"; return this.jdbcTemplate.queryForObject(sql, new Object[]{id}, new SingleColumnRowMapper(String.class)); } @Override public List getStudentNamesByGrade(Integer grade) { String sql = "select name from test_student where grade = ?"; return this.jdbcTemplate.query(sql, new Object[]{grade}, new SingleColumnRowMapper(String.class)); }

测试:

@Test public void getStudentNameById(){ String name = studentService.getStudentNameById("3"); assertEquals("李清照", name); } @Test public void getStudentNamesByGrade(){ List names = studentService.getStudentNamesByGrade(1); assertTrue(2 == names.size()); }

BeanPropertyRowMapper

当查询数据库返回的是多列数据,且你需要将这些多列数据映射到某个具体的实体类上,那么就该使用这个RowMapper,下面是具体的使用代码:

@Override public Student getStudentByName2(String name) { String sql = "select name, gender from test_student where name = ?"; return this.jdbcTemplate.queryForObject(sql, new Object[]{name}, new BeanPropertyRowMapper(Student.class)); } @Override public List getStudentsByName2(String name) { String sql = "select name, gender from test_student where name = ?"; return this.jdbcTemplate.query(sql, new Object[]{name}, new BeanPropertyRowMapper(Student.class));

这种使用方式有一个前提,那就是数据库SQL查出来的数据其列名与实体类中的属性名是一致的,当然个数和顺序可以不一致。比如数据库SQL查出来的姓名列叫name,那么对应的实体类中的姓名也必须叫name,而不能叫studentName或者其它。

定义自己的RowMapper

当然,如果你SQL查询出来的数据列名就是和实体类的属性名不一样,或者想按照自己的规则来装配实体类,那么就可以定义并使用自己的Row Mapper。

public class StudentRowMapper implements RowMapper { @Override public Student mapRow(ResultSet rs, int i) throws SQLException { Student student = new Student(); student.setName(rs.getString("name")); student.setGender(rs.getString("gender")); student.setEmail(rs.getString("email")); return student; } } @Override public Student getStudentByName3(String name) { String sql = "select name, gender, email from test_student where name = ?"; return this.jdbcTemplate.queryForObject(sql, new Object[]{name}, new StudentRowMapper()); } @Override public List getStudentsByName3(String name) { String sql = "select name, gender, email from test_student where name = ?"; return this.jdbcTemplate.query(sql, new Object[]{name}, new StudentRowMapper()); } 小结 Spring提供了JdbcTemplate来简化JDBC操作; 使用JdbcTemplate时,根据需要优先选择高级方法; 任何JDBC操作都可以使用保底的execute(ConnectionCallback)方法。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3